package bubbleindex;

import Jama.Matrix;
import com.nativelibs4java.opencl.CLBuildException;
import com.nativelibs4java.opencl.CLContext;
import com.nativelibs4java.opencl.CLKernel;
import com.nativelibs4java.opencl.CLProgram;
import com.nativelibs4java.opencl.CLQueue;
import com.nativelibs4java.opencl.JavaCL;
import static info.yeppp.Core.Multiply_V64fV64f_V64f;
import static info.yeppp.Core.Subtract_V64fV64f_V64f;
import static info.yeppp.Math.Log_V64f_V64f;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 *
 * @author ttrott
 * BuildIndex class contains the methods to manage the creation of The Bubble Index
 */
public class BuildIndex {
     
    volatile static boolean Stop;
    public static List<Float> ResultList;
    public static String src;
    public static CLContext context;
    public static CLQueue queue;
    public static CLProgram program;
    public static CLKernel addFloatsKernel;
    public static ByteOrder byteOrder;
    /**
     * Create goes through the bubble indices needed and calculates each one
     * @param DAILY_DATA_SIZE the number of price values in the daily price data file
     * @param NUMBER_OF_INDICES the number of bubble indices to create
     */
    public static synchronized void Create(int DAILY_DATA_SIZE, int NUMBER_OF_INDICES) {
        
        if (!BubbleIndex.forceCPU) {
        //First try to find GPU, if no GPU then catch and proceed with CPU
            try {
               // CLContext context = JavaCL.createBestContext();
               // CLQueue queue = context.createDefaultQueue();
                context = JavaCL.createBestContext();
                queue = context.createDefaultQueue();
                BubbleIndex.displayOutput("GPU found. Using GPU.", false);

                try {

                    program = context.createProgram(src);
                    addFloatsKernel = program.createKernel("hq_derivative");
                    byteOrder = context.getByteOrder();
                    for (int i = 0; i < NUMBER_OF_INDICES; i++){			

                        Stop = false;
                        ResultList = new ArrayList<>();
                        List<String> DateList = new ArrayList<>();
                        List<String> DataList = new ArrayList<>();

                        BubbleIndex.PreviousFilePath = Indices.userDir + "ProgramData" + 
                                Indices.filePathSymbol + BubbleIndex.CategoryName + Indices.filePathSymbol + 
                                BubbleIndex.SelectionName + Indices.filePathSymbol + BubbleIndex.SelectionName + 
                                Integer.toString(BubbleIndex.BacktestDayLengths[i]) + "days.csv";

                        if (DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i] <= 1) {
                            Stop = true;
                            continue;
                        }

                        int START_INDEX;

                        if (InputData.checkForFile(BubbleIndex.PreviousFilePath)) {

                            InputData.ReadValues(BubbleIndex.PreviousFilePath, DataList, 
                                DateList, true, true);

                            int UpdateLength = BubbleIndex.DailyPriceDateList.size() 
                                    - updateDateMatch(DateList) - 1;

                            if (Stop) {
                                System.out.println("Dates in the previous file: " + 
                                        BubbleIndex.PreviousFilePath + " do not match. Code 028.");
                                BubbleIndex.displayOutput("Dates in the previous file: " + 
                                        BubbleIndex.PreviousFilePath + " do not match.", false);
                                continue;
                            }

                            START_INDEX = DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i] 
                                    - UpdateLength;   
                        }

                        else {
                            START_INDEX = 0;         
                        }

                        final int numBatches = (int) Math.ceil((DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i] - START_INDEX) / 500.0);
                        
                        for (int batch = 0; batch < numBatches; batch++) {
                            int batchStartIndex = batch * 500 + START_INDEX;
                            int batchEndIndex = (batch + 1) * 500 + START_INDEX;
                            ExecutorService executor = Executors.newFixedThreadPool(BubbleIndex.ThreadNumber);

                            List<Callable<Float>> callables = new ArrayList<>();

                            createCallables(callables, batchStartIndex, Math.min(DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i], batchEndIndex), i);

                            List<Future<Float>> results = executor.invokeAll(callables);

                            for (Future<Float> futures : results) {
                                ResultList.add(futures.get());
                            }
                            
                            executor.shutdown();
                        }
                        //List copy = new ArrayList(ResultList);


                        if (!Stop) {
                            String Name = BubbleIndex.SelectionName + 
                                BubbleIndex.BacktestDayLengths[i] + "days.csv";

                            ExportData.WriteCSV(ResultList, DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i], 
                                Name, InputData.checkForFile(BubbleIndex.PreviousFilePath));
                        }
                    }

                    program.release();
                    addFloatsKernel.release();
                } catch (IOException | InterruptedException | ExecutionException ex) {
                    program.release();
                    addFloatsKernel.release();
                    System.out.println("Exception. Code 011.");
                }

                queue.flush();
                queue.release();
                context.release();

            } catch (CLBuildException th) {
                System.out.println("CLBuildException. Code 012.");            
                BubbleIndex.displayOutput("No GPU found. Using CPU.", false);     
                queue.flush();
                queue.release();
                context.release();
                noGPU.Create(DAILY_DATA_SIZE, NUMBER_OF_INDICES);
            }
        }
        
        else {
            noGPU.Create(DAILY_DATA_SIZE, NUMBER_OF_INDICES);
        }
    }
    
    /**
     * 
     * @param context the GPU context
     * @param addFloatsKernel the GPU kernel
     * @param queue the GPU kernel queue
     * @param callables the list of callables
     * @param START the start index
     * @param SIZE the number of values to calculate
     * @param INDEX the index corresponding to the backtest day length array
     */
    private static void createCallables(List<Callable<Float>> callables, int START, int SIZE, int INDEX) {

        for (int j = START; j < SIZE; j++) {
            callables.add(new MyCallable(j, BubbleIndex.BacktestDayLengths[INDEX]));   
        }                   
    }

    /**
     * updateDateMatch searches the previously created file and compares the
     * last calculated date with the dates in the daily price data
     * @param DateList the list of dates from previous file
     * @return the index value of the Daily Price Data List where date values match
     */
    public static int updateDateMatch(List<String> DateList) {
        final String finalValue = DateList.get(DateList.size() - 1);

        for (int i = 0; i < BubbleIndex.DailyPriceDateList.size(); i++) {
            if (finalValue.equals(BubbleIndex.DailyPriceDateList.get(i))) {    
                return i;
            }
        }
        
        /*Since previous file exists but no dates matched, must be error... 
        start from beginning*/
        Stop = true;
        return 0;                        
    }
    
     /**
     * DataReverse method takes an array and reverses the linear order of the
     * array.
     * @param Data The array to be reversed
     * @param SIZE The size of the array
     */   
    public static void DataReverse(double[] Data, final int SIZE) {
        
        double[] Temp = new double[SIZE];
        for (int i = 0; i < SIZE; i++) {
            Temp[i] = Data[SIZE - 1 - i];
        }
        
        System.arraycopy(Temp, 0, Data, 0, SIZE);      
    }  

    /**
     * 
     * @param SelectedData
     * @param NumberOfDays 
     */
    public static void Normalize(double[] SelectedData, int NumberOfDays) {
		
		//Delcare and initialize array of returns
        double[] ReturnsData = new double[NumberOfDays];
        ReturnsData[0] = 0.0;
        double[] tempOne = new double[NumberOfDays];
        double[] tempTwo = new double[NumberOfDays];
        double[] tempThree = new double[NumberOfDays];
        double[] tempFour = new double[NumberOfDays];
        double[] tempFive = new double[NumberOfDays];
        
        for (int i = 1; i < NumberOfDays; i++) {
            tempOne[i] = SelectedData[i];
            tempTwo[i] = SelectedData[i-1];
            tempThree[i] = 1.0 / SelectedData[i-1];
        }
        
        Subtract_V64fV64f_V64f(tempOne, 0, tempTwo, 0, tempFour, 0, NumberOfDays);
        
        Multiply_V64fV64f_V64f(tempFour, 0, tempThree, 0, tempFive, 0, NumberOfDays);
        
        System.arraycopy(tempFive, 1, ReturnsData, 1, NumberOfDays - 1);

        //Declare and initialize array of normalized price values
        SelectedData[0] = 100.0;

        for (int i = 1; i < NumberOfDays; i++) {
            SelectedData[i] = SelectedData[i-1] * ReturnsData[i] + SelectedData[i-1];
        }

        Log_V64f_V64f(SelectedData, 0, tempFive, 0, NumberOfDays);
        
        System.arraycopy(tempFive, 0, SelectedData, 0, NumberOfDays);

    }

    /**
     * The LinearFit method solves the b = Ax matrix for the x vector. In other
     * words this is a linear regression to fit the equation to the log prices.
	 * The equation is y = b + Ax1 + Bx2 where x1 and x2 are TimeValues_M_power
	 * and LogCosTimeValues respectively.
     * @param Data The array containing the log prices
     * @param TimeValues_M_Power Array of time values raised to the M power
     * @param LogCosTimeValues Array of logcos time values
     * @param Coef Array containing the models fitted coefficients
     * @param SIZE Size of the data window (days)
     */
    public static void LinearFit(double[] Data, double[] TimeValues_M_Power,
            double[] LogCosTimeValues, double[] Coef, final int SIZE){
            
    	double[][] Array = new double[SIZE][3];
            
    	for (int i = 0; i < SIZE; i++) {
    	    Array[i][0] = 1.0;
    	    Array[i][1] = TimeValues_M_Power[i];
    	    Array[i][2] = LogCosTimeValues[i];
        }
    
        Matrix A = new Matrix(Array);
        Matrix b = new Matrix(Data, SIZE);
        Matrix x = A.solve(b);
            
        for (int i = 0; i < 3; i++) {
    	    Coef[i] = x.get(i,0);
        }
    }              
}